export namespace ui {
  export type Style = Partial<CSSStyleDeclaration>

  export interface CommonElementProps {
    innerHTML?: string;
    className?: string;
    style?: Style;
    id?: string;
    onclick?: (e: MouseEvent) => void;
    children?: UINode;
    dataset?: any
  }
  export interface ElementPropsTagNameMap {
    "a": CommonElementProps;
    "abbr": CommonElementProps;
    "address": CommonElementProps;
    "area": CommonElementProps;
    "article": CommonElementProps;
    "aside": CommonElementProps;
    "audio": CommonElementProps;
    "b": CommonElementProps;
    "base": CommonElementProps;
    "bdi": CommonElementProps;
    "bdo": CommonElementProps;
    "blockquote": CommonElementProps;
    "body": CommonElementProps;
    "br": CommonElementProps;
    "button": CommonElementProps & { disabled?: boolean };
    "canvas": CommonElementProps;
    "caption": CommonElementProps;
    "cite": CommonElementProps;
    "code": CommonElementProps;
    "col": CommonElementProps;
    "colgroup": CommonElementProps;
    "data": CommonElementProps;
    "datalist": CommonElementProps;
    "dd": CommonElementProps;
    "del": CommonElementProps;
    "details": CommonElementProps;
    "dfn": CommonElementProps;
    "dialog": CommonElementProps;
    "div": CommonElementProps;
    "dl": CommonElementProps;
    "dt": CommonElementProps;
    "em": CommonElementProps;
    "embed": CommonElementProps;
    "fieldset": CommonElementProps;
    "figcaption": CommonElementProps;
    "figure": CommonElementProps;
    "footer": CommonElementProps;
    "form": CommonElementProps;
    "h1": CommonElementProps;
    "h2": CommonElementProps;
    "h3": CommonElementProps;
    "h4": CommonElementProps;
    "h5": CommonElementProps;
    "h6": CommonElementProps;
    "head": CommonElementProps;
    "header": CommonElementProps;
    "hgroup": CommonElementProps;
    "hr": CommonElementProps;
    "html": CommonElementProps;
    "i": CommonElementProps;
    "iframe": CommonElementProps;
    "img": CommonElementProps & { src?: string };
    "input": CommonElementProps;
    "ins": CommonElementProps;
    "kbd": CommonElementProps;
    "label": CommonElementProps;
    "legend": CommonElementProps;
    "li": CommonElementProps;
    "link": CommonElementProps;
    "main": CommonElementProps;
    "map": CommonElementProps;
    "mark": CommonElementProps;
    "menu": CommonElementProps;
    "meta": CommonElementProps;
    "meter": CommonElementProps;
    "nav": CommonElementProps;
    "noscript": CommonElementProps;
    "object": CommonElementProps;
    "ol": CommonElementProps;
    "optgroup": CommonElementProps;
    "option": CommonElementProps;
    "output": CommonElementProps;
    "p": CommonElementProps;
    "picture": CommonElementProps;
    "pre": CommonElementProps;
    "progress": CommonElementProps;
    "q": CommonElementProps;
    "rp": CommonElementProps;
    "rt": CommonElementProps;
    "ruby": CommonElementProps;
    "s": CommonElementProps;
    "samp": CommonElementProps;
    "script": CommonElementProps;
    "section": CommonElementProps;
    "select": CommonElementProps;
    "slot": CommonElementProps;
    "small": CommonElementProps;
    "source": CommonElementProps;
    "span": CommonElementProps;
    "strong": CommonElementProps;
    "style": CommonElementProps;
    "sub": CommonElementProps;
    "summary": CommonElementProps;
    "sup": CommonElementProps;
    "table": CommonElementProps;
    "tbody": CommonElementProps;
    "td": CommonElementProps;
    "template": CommonElementProps;
    "textarea": CommonElementProps;
    "tfoot": CommonElementProps;
    "th": CommonElementProps;
    "thead": CommonElementProps;
    "time": CommonElementProps;
    "title": CommonElementProps;
    "tr": CommonElementProps;
    "track": CommonElementProps;
    "u": CommonElementProps;
    "ul": CommonElementProps;
    "var": CommonElementProps;
    "video": CommonElementProps;
    "wbr": CommonElementProps;
  }

  export function flat_dataset(obj: any, parent_key: string = '', ret: any = {}): any {
    for (const key in obj) {
      const value = obj[key]
      const inkey = parent_key ? `${parent_key}__${key}` : key;
      if (typeof value === 'string') {
        ret[inkey] = '<string/> ' + value
        continue
      } else if (typeof value === 'boolean') {
        ret[inkey] = '<boolean/> ' + value
        continue
      } else if (typeof value === 'number') {
        ret[inkey] = '<number/> ' + value
        continue
      } else if (typeof value === 'symbol') {
        ret[inkey] = '<symbol/> ' + value.toString()
        continue
      } else if (typeof value === 'object') {
        flat_dataset(value, inkey, ret)
      }
    }
    return ret
  }
  export type UINode = Node | undefined | string | boolean | number | null | UINode[] | (() => UINode);
  export type UIFunc<P> = (props: P) => UINode;
  export function node<P>(tagName: UIFunc<P>, props: P | undefined, children?: UINode): UINode;
  export function node(tagName: 'a', props: Partial<HTMLAnchorElement> | undefined, children?: UINode): HTMLAnchorElement;
  export function node<K extends keyof ElementPropsTagNameMap>(tagName: K, props: ElementPropsTagNameMap[K] | undefined, children?: UINode): HTMLElementTagNameMap[K];
  export function node<K extends keyof ElementPropsTagNameMap, P = any>(tagName: UIFunc<P> | K, props: any = {}, children: UINode = void 0): UINode {
    if (typeof tagName === 'string') {
      const ret = document.createElement(tagName);
      const { dataset, children: _children = children, style, ..._p } = props;
      if (dataset) {
        const s = flat_dataset(dataset)
        for (const key in s) {
          ret.setAttribute('data-' + key, s[key])
        }
      }
      if (style) {
        applyStyle(ret, props.style);
        delete props.style;
      }
      if (_p) Object.assign(ret, _p);
      append_child(ret, _children)
      return ret;
    } else {
      const ret = tagName(props);
      return ret;
    }
  }
  export function applyStyle(ele: HTMLElement, style: Style) {
    ele.setAttribute('style', '')
    Object.assign(ele.style, style);
  }
  const append_child = (ele: HTMLElement, children: UINode) => {
    if (!children || typeof children === 'boolean') return;
    if (typeof children === 'string' || typeof children === 'number' || typeof children === 'bigint') {
      ele.innerText += children;
      return;
    }
    if (typeof children === 'function')
      return append_child(ele, children())
    if (!Array.isArray(children))
      return ele.appendChild(children)
    for (const child of children)
      append_child(ele, child)
  }
}